﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SharePoint.Client;
using System.Net;
using System.Net.Http;

namespace SPWarmUp
{
    public class Program
    {
        #region Fields
        /// <summary>A reference to the log, if enabled, otherwise null.</summary>
        private static TextFileLog _textFileLog;
        /// <summary>The SharePoint client-side context.</summary>
        private static ClientContext _clientContext;
        /// <summary>The absolute URL to the root of the host.</summary>
        private static string _serverRoot;
        /// <summary>The handler for calling HTTP requests to SharePoint.</summary>
        private static HttpClientHandler _handler;
        /// <summary>The client for calling HTTP requests to SharePoint.</summary>
        private static HttpClient _client;
        /// <summary>The hosts file, if multiple web front-end servers are in use.</summary>
        private static HostsFile _hostsFile;
        #endregion

        public static void Main( string[] args )
        {
            try
            {
                // Cleanup old log files, if True
                if( SPWarmUp.Properties.Settings.Default.RemoveOldLogFiles )
                {
                    Console.WriteLine( "Removing old log files" );
                    TextFileLog.Cleanup();
                }

                // Enable logging, if True
                if( SPWarmUp.Properties.Settings.Default.EnableLogging )
                {
                    _textFileLog = new TextFileLog();
                    Console.WriteLine( _textFileLog.Status );
                    if( _textFileLog.IsReady )
                    {
                        Console.WriteLine( "Log file: {0}", _textFileLog.FullPathAndFile );
                    }
                }

                // Define the collection of site collection URLs to warm up
                List<Uri> siteCollections = new List<Uri>();

                // Check for URLs in the command-line arguments
                foreach( string arg in args )
                {
                    Uri result;
                    if( Uri.TryCreate( arg, UriKind.Absolute, out result ) )
                    {
                        siteCollections.Add( result );
                    }
                    else
                    {
                        Console.WriteLine( "\"{0}\" is not a valid URL", arg );
                        if( _textFileLog != null )
                        {
                            _textFileLog.WriteLine( "\"" + arg + "\" is not a valid URL" );
                        }
                    }
                }

                // Check for URLs in the App.config
                foreach( string url in SPWarmUp.Properties.Settings.Default.WarmUpUrls )
                {
                    if( String.IsNullOrEmpty( url ) )
                    {
                        continue;
                    }
                    Uri result;
                    if( Uri.TryCreate( url, UriKind.Absolute, out result ) )
                    {
                        siteCollections.Add( result );
                    }
                    else
                    {
                        Console.WriteLine( "\"{0}\" is not a valid URL", url );
                        if( _textFileLog != null )
                        {
                            _textFileLog.WriteLine( "\"" + url + "\" is not a valid URL" );
                        }
                    }
                }

                // Start crawling if we have URLs to warm up
                if( siteCollections.Count > 0 )
                {
                    WarmUp( siteCollections );
                }
                else
                {
                    Console.WriteLine( "No valid site collection URLs detected" );
                    if( _textFileLog != null )
                    {
                        _textFileLog.WriteLine( "No valid site collection URLs detected" );
                    }
                }
            }
            catch( Exception ex )
            {
                Console.WriteLine( "Exception during Main: {0}", ex.GetType().Name );
                Console.WriteLine( "Exception message:     {0}", ex.Message );
                Console.WriteLine( "Exception stack trace: {0}", ex.StackTrace );
                if( _textFileLog != null )
                {
                    _textFileLog.WriteLine( "Exception during Main: " + ex.GetType().Name );
                    _textFileLog.WriteLine( "Exception message:     " + ex.Message );
                    _textFileLog.WriteLine( "Exception stack trace: " + ex.StackTrace );
                }
            }
            finally
            {
                // Dispose the log file
                using( _textFileLog )
                {
                }
                _textFileLog = null;
            }
        }

        private static void WarmUp( List<Uri> siteCollections )
        {
            // Check for multiple front-end web servers
            if( SPWarmUp.Properties.Settings.Default.FrontEndWebServers.Count > 0 )
            {
                Echo( "Opening the hosts file" );

                // Cache the initial hosts file
                if( !String.IsNullOrEmpty( SPWarmUp.Properties.Settings.Default.CustomHostsFileDirectory ) )
                {
                    _hostsFile = HostsFile.Open( SPWarmUp.Properties.Settings.Default.CustomHostsFileDirectory );
                }
                else
                {
                    _hostsFile = HostsFile.Open();
                }
            }

            // Backup the hosts file
            if( _hostsFile != null )
            {
                if( _hostsFile.Backup() )
                {
                    Echo( "Backed up hosts file" );
                }
                else
                {
                    Echo( "Failed to backup hosts file" );
                }
            }

            // Generate the list of web front-end servers
            List<WebFrontEndServer> wfeList = new List<WebFrontEndServer>();
            if( SPWarmUp.Properties.Settings.Default.FrontEndWebServers.Count > 0 )
            {
                if( _hostsFile.IsBackedUp )
                {
                    foreach( string wfe in SPWarmUp.Properties.Settings.Default.FrontEndWebServers )
                    {
                        wfeList.Add( new WebFrontEndServer() { IpAddress = wfe, IsDefault = false } );
                    }
                }
                else
                {
                    wfeList.Add( new WebFrontEndServer() { IsDefault = true } );
                }
            }
            else
            {
                wfeList.Add( new WebFrontEndServer() { IsDefault = true } );
            }

            // Process each URL for each web front-end server
            foreach( WebFrontEndServer wfe in wfeList )
            {
                // Process each URL
                foreach( Uri url in siteCollections )
                {
                    try
                    {
                        // Echo the web front-end IP address
                        if( !wfe.IsDefault )
                        {
                            Echo( "Working with web front-end server " + wfe.IpAddress );
                        }

                        // Echo to the console and log
                        Echo( "Connecting to SharePoint site collection: " + url.AbsoluteUri );

                        // Update the hosts file
                        if( !wfe.IsDefault )
                        {
                            if( _hostsFile.Update( wfe, url ) )
                            {
                                Echo( "Added hosts entry " + wfe.IpAddress + " for " + url.Authority );
                            }
                            else
                            {
                                Echo( "Failed to update hosts file" );
                            }
                        }

                        // Create the SharePoint context for hierarchy traversal
                        _clientContext = new ClientContext( url );
                        _serverRoot = url.Scheme + "://" + url.Authority;

                        // Create the HTTP client for warm up
                        _handler = new HttpClientHandler();
                        _handler.UseDefaultCredentials = true;
                        _client = new HttpClient( _handler );

                        // Start the warm up process
                        WarmUpSiteCollection( url );
                    }
                    catch( Exception ex )
                    {
                        Console.WriteLine( "Failed to warm up:     {0}", url.AbsoluteUri );
                        Console.WriteLine( "Exception type:        {0}", ex.GetType().Name );
                        Console.WriteLine( "Exception message:     {0}", ex.Message );
                        Console.WriteLine( "Exception stack trace: {0}", ex.StackTrace );
                    }
                }
            }

            // Restore the original hosts file
            if( _hostsFile != null )
            {
                if( _hostsFile.Restore() )
                {
                    Echo( "Successfully restored original hosts file" );
                    _hostsFile.DeleteBackup();
                }
                else
                {
                    Echo( "Failed to restore original hosts file, please restore manually" );
                }
            }
        }

        private static void WarmUpSiteCollection( Uri url )
        {
            bool loaded = false;
            try
            {
                _clientContext.Load( _clientContext.Web, w => w.ServerRelativeUrl, w => w.Title );
                _clientContext.ExecuteQuery();
                loaded = true;
            }
            catch
            {
                Console.WriteLine( "Error loading SharePoint context for site root {0}", url.AbsoluteUri );
                if( _textFileLog != null )
                {
                    _textFileLog.WriteLine( "Error loading SharePoint context for site root " + url.AbsoluteUri );
                }
            }
            if( loaded )
            {
                WarmUpWeb( url );
                WarmUpRecursive( _clientContext.Web );
            }
        }

        private static void WarmUpRecursive( Web web )
        {
            bool loaded = false;
            WebCollection webs = web.Webs;
            try
            {
                _clientContext.Load( webs );
                _clientContext.ExecuteQuery();
                loaded = true;
            }
            catch
            {
                Console.WriteLine( "Access denied loading subsites collection" );
                if( _textFileLog != null )
                {
                    _textFileLog.WriteLine( "Access denied loading subsites collection" );
                }
                loaded = false;
            }
            if( loaded )
            {
                foreach( Web w in webs )
                {
                    bool loadedWeb = false;
                    try
                    {
                        _clientContext.Load( w, q => q.ServerRelativeUrl, q => q.Title );
                        _clientContext.ExecuteQuery();
                        loadedWeb = true;
                    }
                    catch
                    {
                        Console.WriteLine( "Error loading SharePoint context for subsite" );
                        if( _textFileLog != null )
                        {
                            _textFileLog.WriteLine( "Error loading SharePoint context for subsite" );
                        }
                    }
                    if( loadedWeb )
                    {
                        WarmUpWeb( new Uri( _serverRoot + w.ServerRelativeUrl ) );
                        WarmUpRecursive( w );
                    }
                }
            }
        }

        private static void WarmUpWeb( Uri url )
        {
            Console.WriteLine( "Warming up {0}", url.AbsoluteUri );
            if( _textFileLog != null )
            {
                _textFileLog.WriteLine( "Warming up " + url.AbsoluteUri );
            }
            var response = _client.GetAsync( url );
            response.Wait();
            var result = response.Result;
            switch( result.StatusCode )
            {
                case HttpStatusCode.OK:
                    break;
                default:
                    Console.WriteLine( "Error, server responded with {0}", result.StatusCode );
                    if( _textFileLog != null )
                    {
                        _textFileLog.WriteLine( "Error, server responded with " + result.StatusCode );
                    }
                    break;
            }
        }

        private static void Echo( string message )
        {
            Console.WriteLine( message );
            if( _textFileLog != null )
            {
                _textFileLog.WriteLine( message );
            }
        }
    }
}